#pragma once
#include "matrix2.h"


BEGIN_NAMESPACE_OXGE

template <typename T> 
class MATHLIB_TRANSFER Matrix4T
{
public:
	typedef Matrix4T<T> MatrixType;
	typedef Matrix3T<T> SubMatrixType;
	typedef Vector4T<T> VectorType;
	typedef T ValueType;

	//
	Matrix4T(){ }

	//
	Matrix4T(T m00,T m01,T m02, T m03,
			 T m10,T m11,T m12, T m13,
			 T m20,T m21,T m22, T m23,
			 T m30,T m31,T m32, T m33)
	{ 
		Set(m00,m01,m02,m03,
			m10,m11,m12,m13,
			m20,m21,m22,m23,
			m30,m31,m32,m33);
	}

	//
	Matrix4T(const T* cv)
	{
		Set(cv);
	}

	//
	inline MatrixType& Set(T am00,T am01,T am02, T am03,
						   T am10,T am11,T am12, T am13,
						   T am20,T am21,T am22, T am23,
						   T am30,T am31,T am32, T am33)
	{
		m00 = am00; m01 = am01; m02 = am02; m03 = am03;
		m10 = am10; m11 = am11;	m12 = am12;	m13 = am13;
		m20 = am20;	m21 = am21;	m22 = am22; m23 = am23;
		m30 = am30; m31 = am31; m32 = am32; m33 = am33;
		return *this;
	}

	//set val from a c array
	inline MatrixType& Set(const T* cv)
	{
		memcpy(c_v,cv,sizeof(T) * NRow * NCol)
	}

	//Determination of Matrix
	T Det()const
	{ 
		return m00 * GetSubMatrix(0,0).Det() - m01 * GetSubMatrix(0,1).Det() + m02 * GetSubMatrix(0,2).Det() - m03 * GetSubMatrix(0,3).Det();
	}

	//Matrix * Matrix
	MatrixType operator * (const MatrixType& matR)const
	{
		const MatrixType& matL = *this;
		return MatrixType( matL.RowVector(0).dot(matR.ColVector(0)), matL.RowVector(0).dot(matR.ColVector(1)), matL.RowVector(0).dot(matR.ColVector(2)), matL.RowVector(0).dot(matR.ColVector(3)),
						   matL.RowVector(1).dot(matR.ColVector(0)), matL.RowVector(1).dot(matR.ColVector(1)), matL.RowVector(1).dot(matR.ColVector(2)), matL.RowVector(1).dot(matR.ColVector(3)),
						   matL.RowVector(2).dot(matR.ColVector(0)), matL.RowVector(2).dot(matR.ColVector(1)), matL.RowVector(2).dot(matR.ColVector(2)), matL.RowVector(2).dot(matR.ColVector(3)),
						   matL.RowVector(3).dot(matR.ColVector(0)), matL.RowVector(3).dot(matR.ColVector(1)), matL.RowVector(3).dot(matR.ColVector(2)), matL.RowVector(3).dot(matR.ColVector(3)) ); 
	}

	//mat *= mat
	MatrixType& operator *= (const MatrixType& matR){
		return	*this = (*this) * matR;		
	}

	//Matrix * val
	inline MatrixType operator*(const T& rVal)const{
		MatrixType ret = *this;
		return ret*=rVal;		
	}

	//mat *= val
	MatrixType& operator*= (const T& rVal){
		for(size_t i =0;i<NRow*NCol ;++i)
			c_v[i] *= rVal;
		return *this;
	}

	//mat += mat
	MatrixType& operator+=(const MatrixType& rMat){
		for(size_t i =0;i<NRow*NCol ;++i)
			c_v[i] += rMat.c_v[i];
		return *this;
	}	
	//mat -= mat;
	MatrixType& operator-=(const MatrixType& rMat){
		for(size_t i =0;i<NRow*NCol ;++i)
			c_v[i] -= rMat.c_v[i];
		return *this;
	}

	//Mat - mat;
	MatrixType operator-(const MatrixType rMat)const{
		MatrixType ret = *this;
		return ret -= rMat;
	}

	//mat + mat
	inline MatrixType operator+(const MatrixType& rMat)const{
		MatrixType ret = *this;
		return ret+= rMat;
	}

	//mat ^ p
	inline MatrixType operator^(int p)
	{
		MatrixType ret = MatrixType::IDENTITY;
		for(int i=0;i< abs(p) ; ++i)
			ret *= (*this);

		if( p < 0 )
			return ret.GetInverse();

		return ret;
	}

	//mat * v
	inline VectorType operator*(const VectorType& vec)const
	{
		return VectorType( RowVector(0).dot(vec), RowVector(1).dot(vec),RowVector(2).dot(vec),RowVector(3).dot(vec) );
	}

	inline operator ValueType*() { return c_v; }	
	inline operator const ValueType*()const { return c_v; }

	//SubMatrix of [r,c];
	SubMatrixType GetSubMatrix(int r, int c)const;	

	//row of mat
	inline VectorType RowVector(size_t r)const{	return VectorType( (*this)(r,0),(*this)(r,1),(*this)(r,2),(*this)(r,3) ); }
	//col of mat
	inline VectorType ColVector(size_t c)const{	return VectorType( (*this)(0,c),(*this)(1,c),(*this)(2,c),(*this)(3,c) ); }
	//
	void SetRow(size_t r, const VectorType& RVec)
	{
		for(size_t i=0;i<NCol;++i)
			(*this)(r,i) = RVec.c_v[i];
	}
	//
	void SetCol(size_t c, const VectorType& CVec)
	{
		for(size_t i=0;i<NRow;++i)
			(*this)(i,c) = CVec.c_v[i];
	}
	//
	T& operator()(size_t r,size_t c)
	{ 
		assert(r<NRow && c<NCol);			
		return c_v[r*NCol+c]; 
	}
	const T& operator()(size_t r,size_t c)const
	{ 
		assert(r<NRow && c<NCol);		
		return c_v[r*NCol+c]; 
	}

	//
	inline MatrixType GetTranspose()const{
		MatrixType transMat;
		transpose(transMat, *this);
		return transMat;
	}
	//
	MatrixType& SelfTranspose(){ transpose(*this, *this); return *this; }
	//
	inline MatrixType GetInverse()const{
		MatrixType ret = ZERO;
		inverse(ret, *this);
		return ret;	
	}

	//UnitTest
	static void UnitTest();

public:
	
	const static size_t NRow = 4;
	const static size_t NCol = 4;

	union{
		struct{
			T m00,m01,m02,m03;
			T m10,m11,m12,m13;
			T m20,m21,m22,m23;
			T m30,m31,m32,m33;
		};
		T c_v[NRow*NCol];
	};	

	const static MatrixType IDENTITY;
	const static MatrixType ZERO;


};


template<typename T>
void transpose(Matrix4T<T>& dest, const Matrix4T<T>& src)
{	
	if(&dest != &src)
	{
		dest.m01 = src.m10;
		dest.m02 = src.m20;
		dest.m03 = src.m30;
		dest.m10 = src.m01;
		dest.m12 = src.m21;
		dest.m13 = src.m31;
		dest.m20 = src.m02;
		dest.m21 = src.m12;
		dest.m23 = src.m32;
		dest.m30 = src.m03;
		dest.m31 = src.m13;
		dest.m32 = src.m23;

		dest.m00 = src.m00;
		dest.m11 = src.m11;
		dest.m22 = src.m22;
		dest.m33 = src.m33;
	}
	else
	{
		std::swap(dest.m01, dest.m10);
		std::swap(dest.m02, dest.m20);
		std::swap(dest.m03, dest.m30);		
		std::swap(dest.m12, dest.m21);
		std::swap(dest.m13, dest.m31);
		std::swap(dest.m23, dest.m32);
	}
}


template<typename T> 
bool inverse(Matrix4T<T>& dest, const Matrix4T<T>& src);

//global I/O
template<typename T>
std::ostream& operator<<(std::ostream& os, const Matrix4T<T>& mat);

template<typename T>
std::istream& operator>>(std::istream& is, Matrix4T<T>& vec);


END_NAMESPACE_OXGE